﻿using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace UCS.XNA.CombatBiplanes.Synchronization
{
    public class ServerGameSynchronizer : GameSynchronizer
    {
        #region Private Members

        private TcpListener _listener;
        private Player _currentPlayer;
        private readonly int _maximumPlayers;
        private CombatBiplanes _game;

        #endregion

        #region Constructors

        public ServerGameSynchronizer(CombatBiplanes game, int maximumPlayers, CommandConverter converter)
        {
            this._connectedClients = new Dictionary<Player, TcpClient>();
            this._playersBuffers = new Dictionary<Player, byte[]>();
            this._playersTotalBytesReceived = new Dictionary<Player, int>();

            this._listener = new TcpListener(IPAddress.Any, SERVER_PORT);

            this._game = game;
            this._maximumPlayers = maximumPlayers;
            this.Converter = converter;

            this._currentPlayer = this.CreateNewPlayer();

            this._connectedClients.Add(this._currentPlayer, null);
            this._playersBuffers.Add(this._currentPlayer, null);
            this._playersTotalBytesReceived.Add(this._currentPlayer, 0);

            this.SynchronizeNewPlayer(this._currentPlayer);
            this.SendActionToPlayer(this._currentPlayer, ActionType.YouAre, this._game, new int[] { this._currentPlayer.Number } );
        }

        #endregion

        #region Public Methods

        public override void Update(GameTime gameTime)
        {
            this.CheckPendingClient();

            foreach (byte[] command in this.ReceiveCommand())
            {
                if (command != null)
                {       
                    ActionType actionType = this.Converter.ToActionType(command);
                    IActionExecutor executor = this.Converter.ToActionExecutor(command);
                    int[] parameters = this.Converter.ToParameter(command);

                    this.SendActionToEveryone(actionType, executor, parameters);
                }
            }
        }

        public override void Start()
        {
            try
            {
                this._listener.Start();
            }
            catch (SocketException ex)
            {
                base.RaiseSocketError(ex.Message);
            }
        }

        public override void Stop()
        {
            this._listener.Stop();

            base.Stop();
        }

        public override void SendActionToEveryone(ActionType actionType, IActionExecutor executor, int[] parameters)
        {
            byte[] command = this.Converter.ToCommand(actionType, executor, parameters);

            foreach (var client in this._connectedClients.Values)
            {
                if (client != null)
                    this.SendCommand(client, command);
            }
            
            executor.Execute(actionType, parameters);
        }

        protected override void SendActionToPlayer(Player player, ActionType actionType, IActionExecutor executor, int[] parameter)
        {
            byte[] command = this.Converter.ToCommand(actionType, executor, parameter);

            TcpClient client = this._connectedClients[player];

            if (client != null)
                this.SendCommand(client, command);
            else
                executor.Execute(actionType, parameter);
        }

        #endregion

        #region Private Methods

        private Player CreateNewPlayer()
        {
            if (this._connectedClients.Keys.Count() < this._maximumPlayers)
            {
                int playerNumber = this._connectedClients.Keys.Count() + 1;
                Player player = new Player(playerNumber, 3);

                return player;
            }

            throw new Exception();
        }

        private void SynchronizeNewPlayer(Player newPlayer)
        {
            this.SendActionToEveryone(ActionType.Connect, this._game, new int[] { newPlayer.Number });
            this.SendActionToPlayer(newPlayer, ActionType.YouAre, this._game, new int[] { newPlayer.Number });

            foreach (var player in this._connectedClients.Keys)
            {
                if (player != newPlayer)
                    this.SendActionToPlayer(newPlayer, ActionType.Connect, this._game, new int[] { player.Number });
            }
        }

        private void CheckPendingClient()
        {
            if (this._listener.Pending())
            {
                TcpClient client = this._listener.AcceptTcpClient();
                Player newPlayer = this.CreateNewPlayer();
                this._connectedClients.Add(newPlayer, client);
                this._playersBuffers.Add(newPlayer, new byte[5]);
                this._playersTotalBytesReceived.Add(newPlayer, 0);

                this.SynchronizeNewPlayer(newPlayer);
            }
        }

        #endregion
    }
}
